/*
 * (C) Copyright 2015 by fr3ts0n <erwin.scheuch-heilig@gmx.at>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

package com.fr3ts0n.ecu.gui.application;

import com.fr3ts0n.ecu.EcuDataPv;
import com.fr3ts0n.pvs.ProcessVar;
import com.fr3ts0n.pvs.PvChangeEvent;
import com.fr3ts0n.pvs.PvChangeListener;
import com.fr3ts0n.pvs.PvList;

import org.jfree.data.time.Second;
import org.jfree.data.time.TimeSeries;

import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;

import javax.swing.JPanel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;


/**
 * Panel to combine tabular- and graphical display for OBD data
 *
 * @author erwin
 */
public class ObdDataPanel extends JPanel
	implements PvChangeListener, ListSelectionListener
{
	/**
	 *
	 */
	private static final long serialVersionUID = 4441869977997912421L;
	// selectable PID's'
	HashMap<Object, TimeSeries> selPids = new HashMap<>();

	/** Creates new form ObdGraphPanel */
	public ObdDataPanel()
	{
		initComponents();
		tblPids.setPvModel(new ObdItemTableModel());
		tblPids.getSelectionModel().addListSelectionListener(this);
		tblPids.setDefaultRenderer(EcuDataPv.class, new ObdItemTableRenderer());
	}

	/**
	 * Creates new form ObdGraphPanel
	 *
	 * @param pvList List of Process vars (PIDS) to display
	 */
	public ObdDataPanel(PvList pvList)
	{
		initComponents();
		tblPids.setPvModel(new ObdItemTableModel());
		tblPids.getSelectionModel().addListSelectionListener(this);
		tblPids.setDefaultRenderer(EcuDataPv.class, new ObdItemTableRenderer());
		setPidPvs(pvList);
	}

	/**
	 * This method is called from within the constructor to
	 * initialize the form.
	 * WARNING: Do NOT modify this code. The content of this method is
	 * always regenerated by the Form Editor.
	 */
	// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
	private void initComponents()
	{
		java.awt.GridBagConstraints gridBagConstraints;
		
		javax.swing.JSplitPane jSplitPane1 = new javax.swing.JSplitPane();
		JPanel panGraph = new JPanel();
		plotter = new com.fr3ts0n.ecu.gui.application.ObdDataPlotter();
		JPanel jPanel1 = new JPanel();
		JPanel jPanel2 = new JPanel();
		slGraphTime = new javax.swing.JSlider();
		lblHistTime = new javax.swing.JLabel();
		// Variables declaration - do not modify//GEN-BEGIN:variables
		javax.swing.JButton btnClearHist = new javax.swing.JButton();
		JPanel panTable = new JPanel();
		javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane();
		tblPids = new com.fr3ts0n.pvs.gui.PvTable();

		setFont(new java.awt.Font("Dialog", 0, 10));
		setName("DataPanel"); // NOI18N
		setLayout(new java.awt.BorderLayout());

		jSplitPane1.setDividerLocation(0);
		jSplitPane1.setDividerSize(8);
		jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
		jSplitPane1.setResizeWeight(0.5);
		jSplitPane1.setToolTipText("Move to show graph display");
		jSplitPane1.setFont(new java.awt.Font("Dialog", 0, 10));
		jSplitPane1.setName("splitter"); // NOI18N
		jSplitPane1.setOneTouchExpandable(true);

		panGraph.setFont(new java.awt.Font("Dialog", 0, 10));
		panGraph.setLayout(new java.awt.BorderLayout());

		plotter.setToolTipText("Select data items in Table to activate graphing");
		plotter.setFont(new java.awt.Font("Dialog", 0, 10));
		plotter.setName("plotter"); // NOI18N
		panGraph.add(plotter, java.awt.BorderLayout.CENTER);

		jPanel1.setFont(new java.awt.Font("Dialog", 0, 10)); // NOI18N
		jPanel1.setLayout(new java.awt.BorderLayout());

		jPanel2.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.LOWERED));
		jPanel2.setToolTipText("maximum history time in minutes");
		jPanel2.setLayout(new java.awt.GridBagLayout());

		slGraphTime.setFont(new java.awt.Font("Dialog", 0, 10));
		slGraphTime.setMaximum(180);
		slGraphTime.setValue(180);
		slGraphTime.addChangeListener(new javax.swing.event.ChangeListener()
		{
			public void stateChanged(javax.swing.event.ChangeEvent evt)
			{
				slGraphTimeStateChanged();
			}
		});
		gridBagConstraints = new java.awt.GridBagConstraints();
		gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
		gridBagConstraints.weightx = 1.0;
		jPanel2.add(slGraphTime, gridBagConstraints);

		lblHistTime.setFont(new java.awt.Font("Dialog", 0, 10));
		lblHistTime.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
		lblHistTime.setText(String.valueOf(slGraphTime.getValue()) + " min");
		lblHistTime.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 3, 1, 3));
		lblHistTime.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
		gridBagConstraints = new java.awt.GridBagConstraints();
		gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
		jPanel2.add(lblHistTime, gridBagConstraints);

		jPanel1.add(jPanel2, java.awt.BorderLayout.CENTER);

		btnClearHist.setFont(new java.awt.Font("Dialog", 0, 10));
		btnClearHist.setMnemonic('C');
		btnClearHist.setText("Clear History");
		btnClearHist.setToolTipText("Clear history buffer for graphing");
		btnClearHist.addActionListener(new java.awt.event.ActionListener()
		{
			public void actionPerformed(java.awt.event.ActionEvent evt)
			{
				btnClearHistActionPerformed();
			}
		});
		jPanel1.add(btnClearHist, java.awt.BorderLayout.EAST);

		panGraph.add(jPanel1, java.awt.BorderLayout.SOUTH);

		jSplitPane1.setLeftComponent(panGraph);

		panTable.setFont(new java.awt.Font("Dialog", 0, 10));
		panTable.setLayout(new java.awt.BorderLayout());

		tblPids.setAutoResizeMode(5);
		tblPids.setFont(new java.awt.Font("Dialog", 0, 10));
		tblPids.setName("DataTable"); // NOI18N
		tblPids.setShowGrid(true);
		tblPids.setToolTipText("Select items to start graphing");
		jScrollPane1.setViewportView(tblPids);

		panTable.add(jScrollPane1, java.awt.BorderLayout.CENTER);

		jSplitPane1.setBottomComponent(panTable);

		add(jSplitPane1, java.awt.BorderLayout.CENTER);
	}// </editor-fold>//GEN-END:initComponents

	private void btnClearHistActionPerformed()//GEN-FIRST:event_btnClearHistActionPerformed
	{//GEN-HEADEREND:event_btnClearHistActionPerformed
		TimeSeries ts;
		Iterator<TimeSeries> it = selPids.values().iterator();
		while (it.hasNext())
		{
			ts = it.next();
			ts.clear();
		}
	}//GEN-LAST:event_btnClearHistActionPerformed

	private void slGraphTimeStateChanged()//GEN-FIRST:event_slGraphTimeStateChanged
	{//GEN-HEADEREND:event_slGraphTimeStateChanged
		TimeSeries ts;
		lblHistTime.setText(String.valueOf(slGraphTime.getValue()) + " min");
		if (!slGraphTime.getValueIsAdjusting())
		{
			Iterator<TimeSeries> it = selPids.values().iterator();
			while (it.hasNext())
			{
				ts = it.next();
				ts.setMaximumItemAge(slGraphTime.getValue() * 60);
			}
		}
	}//GEN-LAST:event_slGraphTimeStateChanged

	/**
	 * get readable unique String from PV
	 */
	private Object getPvId(ProcessVar pv)
	{
		return (pv.getKeyValue());
	}
	
	
	private javax.swing.JLabel lblHistTime;
	private com.fr3ts0n.ecu.gui.application.ObdDataPlotter plotter;
	private javax.swing.JSlider slGraphTime;
	private com.fr3ts0n.pvs.gui.PvTable tblPids;
	// End of variables declaration//GEN-END:variables

	/**
	 * Holds value of property pidPvs.
	 */
	private PvList pidPvs;

	/**
	 * Getter for property pidPvs.
	 *
	 * @return Value of property pidPvs.
	 */
	public PvList getPidPvs()
	{
		return this.pidPvs;
	}

	/**
	 * Setter for property pidPvs.
	 *
	 * @param pidPvs New value of property pidPvs.
	 */
	@SuppressWarnings("unchecked")
	public void setPidPvs(PvList pidPvs)
	{
		TimeSeries ts;
		// if there is an o previous instance registered, unregister ...
		if (this.pidPvs != null)
			this.pidPvs.removePvChangeListener(this);
		this.pidPvs = pidPvs;
		tblPids.setProcessVar(pidPvs);
		tblPids.setDefaultRenderer(Object.class, new ObdItemTableRenderer());
		pidPvs.addPvChangeListener(this);

		// update all TimeSeries with PIDs from PV-List
		plotter.dataset.removeAllSeries();
		selPids.clear();
		Iterator<EcuDataPv> it = pidPvs.values().iterator();
		while (it.hasNext())
		{
			EcuDataPv pv = it.next();
			// create new data series
			ts = new TimeSeries(String.valueOf(pv.get(EcuDataPv.FID_DESCRIPT)));
			ts.setDescription(String.valueOf(pv.get(EcuDataPv.FID_DESCRIPT)));
			ts.setRangeDescription(String.valueOf(pv.get(EcuDataPv.FID_UNITS)));
			// set graph time of new element
			ts.setMaximumItemAge(slGraphTime.getValue() * 60);
			// and add to selectable PID list
			selPids.put(getPvId(pv), ts);
		}
		updateColumnWidths();
	}

	/**
	 * set a new graph title
	 *
	 * @param newTitle new graph title
	 */
	public void setTitle(String newTitle)
	{
		plotter.setTitle(newTitle);
	}

	/**
	 * handle changes in the process var(s)
	 *
	 * @param event Process var event to be handled
	 */
	public void pvChanged(PvChangeEvent event)
	{
		TimeSeries ts;
		EcuDataPv pv;

		switch (event.getType())
		{
			case PvChangeEvent.PV_MODIFIED:
				pv = (EcuDataPv) event.getValue();
				if ((ts = selPids.get(getPvId(pv))) != null)
					try
					{
						ts.addOrUpdate(new Second(), ((Number)pv.get(EcuDataPv.FID_VALUE)).floatValue());
					} catch (Exception e)
					{
						ProcessVar.log.log(Level.SEVERE, "", e);
					}
				break;

			case PvChangeEvent.PV_DELETED:
				// remove from selectable PIDs
				pv = (EcuDataPv) event.getValue();
				selPids.remove(pv);
				break;

			case PvChangeEvent.PV_CLEARED:
				// remove all from selectable PIDs
				selPids.clear();
				break;

			case PvChangeEvent.PV_ADDED:
				if ((event.getValue() instanceof EcuDataPv))
				{
					pv = (EcuDataPv) event.getValue();
					addDataSeries(pv);
				} else if ((event.getValue() instanceof Object[]))
				{
					for (Object currPv : (Object[]) event.getValue())
					{
						addDataSeries((ProcessVar) currPv);
					}
				}
				break;
		}
		// update table column widths
		updateColumnWidths();
	}


	private void addDataSeries(ProcessVar pv)
	{
		TimeSeries ts;
		// create new data series
		ts = new TimeSeries(String.valueOf(pv.get(EcuDataPv.FID_DESCRIPT)),
			null,
			String.valueOf(pv.get(EcuDataPv.FID_UNITS)));
		// set graph time of new element
		ts.setMaximumItemAge(slGraphTime.getValue() * 60);
		// and add to selectable PID list
		selPids.put(getPvId(pv), ts);
	}

	/**
	 * update the column widths of data table
	 */
	private void updateColumnWidths()
	{
		if (tblPids.getRowCount() >= 1)
		{
			/** set column sizes here, since this only works with inserted data */
			tblPids.getColumn(EcuDataPv.FIELDS[EcuDataPv.FID_PID]).setPreferredWidth(40);
			tblPids.getColumn(EcuDataPv.FIELDS[EcuDataPv.FID_OFS]).setPreferredWidth(40);
			tblPids.getColumn(EcuDataPv.FIELDS[EcuDataPv.FID_DESCRIPT]).setPreferredWidth(350);
			tblPids.getColumn(EcuDataPv.FIELDS[EcuDataPv.FID_VALUE]).setPreferredWidth(150);
		}
	}

	/**
	 * update specified column of all available table rows
	 *
	 * @param columnId - column id to be updated
	 */
	public void updateAllTableRows(int columnId)
	{
		((ObdItemTableModel) tblPids.getPvModel()).updateAllRows();
	}

	/**
	 * handle change in table selection
	 * activate / deactivate graphing of selected data items
	 */
	public void valueChanged(ListSelectionEvent e)
	{
		TimeSeries ts;
		int[] selIDs;

		// clean up dataset
		plotter.removeAllSeries();
		// get selected items as list of data
		// rather than array of ID's'
		selIDs = tblPids.getSelectedRows();
		for (int i = 0; i < selIDs.length; i++)
		{
	  /* since table model changed, it returns the complete object for any field */
			EcuDataPv currPid = (EcuDataPv) tblPids.getModel().getValueAt(selIDs[i], EcuDataPv.FID_PID);
			ts = selPids.get(currPid.get(EcuDataPv.FID_PID));
			// if Series  found
			if (ts != null)
			{
				// add new series to plotter ..
				plotter.addSeries(ts);
			}
		}
	}

	@Override
	public String toString()
	{
		return (this.getName());
	}
}
